#import "ModuleMgr.h"
#import "TokenString.h"
#import "Directory.h"
#import "NXGets.h"

@implementation ModuleMgr


- initForPaths: (const char *)aPath fileType: (const char *)aType
{
   [super initKeyDesc: "*"];

   paths = [[StringStorage alloc] init: aPath];

   if(!*[paths stringValue])
   {
      [paths setStringValue: "/LocalLibrary/%s:~/Library/%s"];
      [paths replaceSubstring: "%s" with: [NXApp appName]];
      [paths replaceSubstring: "%s" with: [NXApp appName]];
   }

   NXLogError("Looking for modules in %s", [paths stringValue]);
   fileType = [[StringStorage alloc] init: aType];

   features = (Protocol *)0;
   [self loadModules];

   return self;
}

- setFeatures: (Protocol *) aProtocol
{
   features = aProtocol;
   return self;
}


- loadModules
{
   int cnt;
   const char *aPath;
   id modules = [[Directory alloc] init];
   id path = [[TokenString alloc] init: [paths stringValue]];
   id pattern = [[StringStorage alloc] init: "*."];      

   [path setSeparator: ':'];
   [pattern appendStringValue: [fileType stringValue]];

   while(aPath = [path popStringValue])
   {
      [modules setPath: aPath];

      if([modules count] && ( cnt = [modules select: [pattern stringValue]]))
      {
	 int x;
	 id sendModules = [modules selectedFiles];
	 id modulePath;
	 char *moduleName;

	 /*
	  * Create a hash of the modules. Use the name and path until the module is needed
	  * And the the path will be converted to a bundle.
	  */
	 for(x = 0; x < cnt; x++)
	 {
	    NX_MALLOC(moduleName, char,[[sendModules objectAt: x] count]);
	    strcpy(moduleName, [[sendModules objectAt: x] stringValue]);
	    modulePath = [[StringStorage alloc] init: [modules path]];
	    [modulePath appendCharValue: '/'];
	    [modulePath appendStringValue: moduleName];
	    moduleName[strlen(moduleName) - (strlen([fileType stringValue]) + 1)] = (char)0; /* clip off .sndO */
	    [self insertKey: moduleName value: modulePath];
	    if(![self isKey: moduleName])
		NXLogError("Hash failed\n");
	 }
      }
   }
   [pattern free];
   [modules free];
   [path free];
   return self;
}

- loadFileType: (const char *)aType
{
   return self;
}

- free
{
   [paths free];
   [fileType free];
   return [super free];
}


- getModule: (const char *)moduleName
{
   id bundle = (id)[self valueForKey: moduleName];
   id loaded, primeClass;
   char modulePath[MAXPATHLEN];
   char *newKey;
   
   if(!bundle)
   {   
      const char *aKey;

      [self initState]; 
      
      NXLogError("Could not find hash entry: (%s)\n", moduleName);
      while (aKey = [self nextKey]);
	  NXLogError("moduleName: %s", aKey);

      return nil;
   }
   
   if([bundle isKindOf: [NXBundle class]])
       return bundle;

   if(![bundle isKindOf: [StringStorage class]])
   {
      NXLogError("Hash stored bad item...");
      return nil;
   }
   
   /* Bundle was not yet loaded - do so. */
   NXLogError("Need to load: %s", [bundle stringValue]);

   loaded = [[NXBundle alloc] initForDirectory: [bundle stringValue]];

   if(!loaded)
   {
      NXLogError("failed to load\n");
      return nil;
   }
      
   /* insert the real bundle into the hash now to replace the place holder */
   NX_MALLOC(newKey, char, strlen(moduleName));	/* allocate another key - this is a bug in HashTable I feel */
   strcpy(newKey, moduleName);
   [self insertKey: newKey value: loaded];

   [bundle free];			     /* free up the path/place holder */
   bundle = loaded;

   /*
    * Before we resolve the symbols in the bundle see if it needs any
    * other bundles loaded as ancestors.
    */
   if([bundle getPath: modulePath forResource: "inheritance" ofType: ""])
   {
      NXStream *sp;
      char parentsBuf[128];
      const char *parent;
      id parents;

      sp = NXMapFile(modulePath, NX_READONLY);

      if(!sp)
      {
	 NXLogError("Couldn't open %s\n", [bundle stringValue]);
	 return nil;
      }
      NXGets(parentsBuf, 128, sp);
      NXClose(sp);

					     /* Get known parrents and load them */
      parents = [[TokenString alloc] init: parentsBuf];
      while(parent = [parents popStringValue])
	  if(![self getModule: parent])
	  {
	     NXLogError("Ancestor failed to load...\n");
	     return nil;
	  }
	 
      [parents free];
   }

   if(!(primeClass = [bundle principalClass]))
   {					     /* make rld resolve names */
      NXLogError("Corruoted bundle: %s\n", moduleName);
      return nil;
   }

   if(features)
   {
      if(![primeClass conformsTo: features])    
      {
	 NXLogError("Bundle's principal class does not conform to %s protocol.", [features name]);
	 return nil;
      }
   }

   return bundle;
}

- (NXHashState) initState
{
   state = [super initState];    
   return state;
}

- (const char *)nextKey
{
   static const void  *key, *value; 

   if([self nextState: &state key: &key value: &value])
       return (const char *)key;
   else
       return (const char *)0;
}


@end
